import java.util.*;
import java.io.PrintStream;
import java.io.FileOutputStream;



class MIPSException extends Exception
{
    public MIPSException()
    {
	super("RTL code isn't valid");
    }
}

class MIPS {
    private static Map<String, String> symbolTable = new HashMap<String, String>();
    
    public static void generateMIPSCode(List<RtlDec> program, String fileName) throws MIPSException
    {
	String mipsCode = new String();
	Iterator<RtlDec> iterator = program.iterator();
	RtlDec rtlDec;
	
	try{
	    FileOutputStream oStream = new FileOutputStream(fileName);
	    PrintStream mipsOut = new PrintStream(oStream);

	    while ( iterator.hasNext() ){
		rtlDec = iterator.next();
	    RtlDecId rtlDecId = rtlDec.getRtlDecId();
	    switch(rtlDecId)
	    {
	        case VARDEF:
		    {
			//System.out.println(rtlDecId);
			mipsOut.println(".data");
			Data varDef = (Data)rtlDec;
			int size = varDef.getSize();
			if(size == Rtl.sizeOf(RtlType.BYTE))
			{
			    mipsOut.println(varDef.getLabel() + ":");
			    mipsOut.println("    " + ".space " + varDef.getSize());
			}
			else //if(size = Rtl.sizeOf(RtlType.LONG))
			{
			    mipsOut.println("    " + ".align 2");
			    mipsOut.println(varDef.getLabel() + ":");
			    mipsOut.println("    " + ".space " + varDef.getSize());			
			}
			break;
		    }
	        case PROCDEF:
		    {
			symbolTable.clear();
			symbolTable.put(Temp.getFP().toString(), "$fp");
			Proc prcDef = (Proc)rtlDec;

			mipsOut.println(".text");
			if(prcDef.getLabel().compareTo("Pmain")==0)
			    mipsOut.println("main:");
			else
			    mipsOut.println(prcDef.getLabel() + ":");
			
			if(prcDef.getFrame() !=0)
			{
			    
			    mipsOut.println("#[Allocate space for local variables");
			    mipsOut.println("    " + "li $t0, " + prcDef.getFrame());//aligned when generating rtl code
			    mipsOut.println("    " + "sub $sp, $sp, $t0");
			    mipsOut.println("#]");
			}

			mipsOut.println("#[Save registers and update FP");
			mipsOut.println("    " + "sw $fp, " + "($sp)" + "\t #push fp of caller into stack");
			mipsOut.println("    " + "li $t0, 4");
			mipsOut.println("    " + "sub $sp, $sp, $t0");			

			mipsOut.println("    " + "addi $fp, $sp, " + (prcDef.getFrame() + 4) + "\t #update fp value");

			mipsOut.println("    " + "sw $ra, " + "($sp)" + "\t #push ra of caller into stack");
			mipsOut.println("    " + "li $t0, 4");
			mipsOut.println("    " + "sub $sp, $sp, $t0");
			mipsOut.println("#]");


			//Computing addresses for formal parameters.
			List<Temp> paraList = prcDef.getFormals();
			Iterator<Temp> paraIter = paraList.iterator();
			int counter = 4; //SP always point to the first free word.
			while(paraIter.hasNext()){
			    Temp para = paraIter.next();
			    symbolTable.put(para.toString(), counter + "($fp)");
			    counter += 4;
			}

			List<Temp> localList = prcDef.getLocals();
			ListIterator localIter = localList.listIterator(localList.size());
			counter = (localList.size() + 1) * 4;    //+1 since RV is also treated as a temp.

			mipsOut.println("#[Allocate space for temps");
			while(localIter.hasPrevious()){
			    Temp local = (Temp)localIter.previous();
			    mipsOut.println("    " + "li $t0, 4");
			    mipsOut.println("    " + "sub $sp, $sp, $t0");				    
			    symbolTable.put(local.toString(), counter + "($sp)");
			    counter -= 4;
			}

			mipsOut.println("    " + "li $t0, 4");
			mipsOut.println("    " + "sub $sp, $sp, $t0");	
			symbolTable.put(Temp.getRV().toString(), counter + "($sp)");
			mipsOut.println("#]");

			mipsOut.println("#[Procedure body");
			List<RtlInsn> instructions = prcDef.getInsns();
			Iterator<RtlInsn> insIterator = instructions.iterator();
			while(insIterator.hasNext())
			{
			    RtlInsn rtlIns = insIterator.next();
			    RtlInstrId rtlInstrId = rtlIns.getRtlId();
			    //System.out.println("        " + rtlInstrId);
			    switch(rtlInstrId)
			    {
			    case CALL:
				{
				    Call callRtl = (Call)rtlIns;
				    List<Temp> args = callRtl.getArgs();
				    ListIterator argsIter = args.listIterator(args.size());//minus 1?
				    if(argsIter.hasPrevious())
					mipsOut.println("    " + "move $t9, $sp");
				    while(argsIter.hasPrevious()){
					Temp arg  = (Temp) argsIter.previous();					
					mipsOut.println("    " + "lw $t0, " + getAddress(arg).replace("$sp","$t9")  + "\t #load " + arg + "contents");
					mipsOut.println("    " + "sw $t0, " + "($sp)" + "\t #push a arg of callee into stack");
					mipsOut.println("    " + "li $t1, 4");
					mipsOut.println("    " + "sub $sp, $sp, $t1");
				    }
				    mipsOut.println("    " + "jal " + callRtl.getLabel() + "\t #goto the procedure");
				    mipsOut.println("    " + "addi $sp, $sp, " + args.size() * 4);
				    if((callRtl.getTemp().toString()).compareTo(Temp.getVOID().toString()) != 0)
					mipsOut.println("    " + "sw $v0, " + getAddress(callRtl.getTemp())  + "\t #store returned result");
				    break;
				}
			    case LOAD_BYTE:
				{
				    Load loadRtl = (Load)rtlIns;
				    mipsOut.println("    " + "lw $t0, " + getAddress(loadRtl.getAddr())  + "\t #address in $t0");
				    mipsOut.println("    " + "lb $t1, " + "($t0)" + "\t #load byte into $t1");
				    mipsOut.println("    " + "sw $t1, " + getAddress(loadRtl.getDest())  + "\t #store byte into destination address");
				    break;
				}

			    case LOAD_WORD:
				{
				    Load loadRtl = (Load)rtlIns;
				    mipsOut.println("    " + "lw $t0, " + getAddress(loadRtl.getAddr())  + "\t #address in $t0");
				    mipsOut.println("    " + "lw $t1, " + "($t0)" + "\t #load word into $t1");
				    mipsOut.println("    " + "sw $t1, " + getAddress(loadRtl.getDest())  + "\t #store word into destination address");
				    break;
				    
				}
			    case JUMP:
				{
				    Jump jumpRtl = (Jump)rtlIns;
				    mipsOut.println("    " + "b " + jumpRtl.getLabel());			    
				    break;
				}
			    case EVAL_ADD:
				{
				    Eval evalRtl = (Eval)rtlIns;
				    Binary binaryExpRtl = (Binary)evalRtl.getExp();
				    String evalMIPSr, evalMIPSl;				    
				    evalMIPSr = (binaryExpRtl.getRight().isPhysicalRegister())? "move":"lw";
				    evalMIPSl = (binaryExpRtl.getLeft().isPhysicalRegister())? "move":"lw";

				    mipsOut.println("    " + evalMIPSr + " $t0, " + getAddress(binaryExpRtl.getRight())  + "\t #right operand value in $t0");
				    mipsOut.println("    " + evalMIPSl +" $t1, " + getAddress(binaryExpRtl.getLeft())  + "\t #left operand value in $t1");
				    mipsOut.println("    " + "add $t2, $t1, $t0");
				    mipsOut.println("    " + "sw $t2, " + getAddress(evalRtl.getTemp())  + "\t #store word into destination address");
				    break;
				}
			    case EVAL_SUB:
				{
				    Eval evalRtl = (Eval)rtlIns;
				    Binary binaryExpRtl = (Binary)evalRtl.getExp();
				    String evalMIPSr, evalMIPSl;				    
				    evalMIPSr = (binaryExpRtl.getRight().isPhysicalRegister())? "move":"lw";
				    evalMIPSl = (binaryExpRtl.getLeft().isPhysicalRegister())? "move":"lw";

				    mipsOut.println("    " + evalMIPSr + " $t0, " + getAddress(binaryExpRtl.getRight())  + "\t #right operand value in $t0");
				    mipsOut.println("    " + evalMIPSl + " $t1, " + getAddress(binaryExpRtl.getLeft())  + "\t #left operand value in $t1");
				    mipsOut.println("    " + "sub $t2, $t1, $t0");
				    mipsOut.println("    " + "sw $t2, " + getAddress(evalRtl.getTemp())  + "\t #store word into destination address");
				    break;
				}
			    case EVAL_DIV:
				{
				    Eval evalRtl = (Eval)rtlIns;
				    Binary binaryExpRtl = (Binary)evalRtl.getExp();
				    mipsOut.println("    " + "lw $t0, " + getAddress(binaryExpRtl.getRight())  + "\t #right operand value in $t0");
				    mipsOut.println("    " + "lw $t1, " + getAddress(binaryExpRtl.getLeft())  + "\t #left operand value in $t1");
				    mipsOut.println("    " + "div $t2, $t1, $t0");
				    mipsOut.println("    " + "sw $t2, " + getAddress(evalRtl.getTemp())  + "\t #store word into destination address");
				    break;
				}
			    case EVAL_MUL:
				{
				    Eval evalRtl = (Eval)rtlIns;
				    Binary binaryExpRtl = (Binary)evalRtl.getExp();
				    mipsOut.println("    " + "lw $t0, " + getAddress(binaryExpRtl.getRight())  + "\t #right operand value in $t0");
				    mipsOut.println("    " + "lw $t1, " + getAddress(binaryExpRtl.getLeft())  + "\t #left operand value in $t1");
				    mipsOut.println("    " + "mul $t2, $t1, $t0");
				    mipsOut.println("    " + "sw $t2, " + getAddress(evalRtl.getTemp())  + "\t #store word into destination address");
				    break;
				}
			    case EVAL_EQ:
				{
				    Eval evalRtl = (Eval)rtlIns;
				    Binary binaryExpRtl = (Binary)evalRtl.getExp();
				    mipsOut.println("    " + "lw $t0, " + getAddress(binaryExpRtl.getRight())  + "\t #right operand value in $t0");
				    mipsOut.println("    " + "lw $t1, " + getAddress(binaryExpRtl.getLeft())  + "\t #left operand value in $t1");
				    mipsOut.println("    " + "seq $t2, $t1, $t0");
				    mipsOut.println("    " + "sw $t2, " + getAddress(evalRtl.getTemp())  + "\t #store word into destination address");
				    break;
				}
			    case EVAL_NE:
				{
				    Eval evalRtl = (Eval)rtlIns;
				    Binary binaryExpRtl = (Binary)evalRtl.getExp();
				    mipsOut.println("    " + "lw $t0, " + getAddress(binaryExpRtl.getRight())  + "\t #right operand value in $t0");
				    mipsOut.println("    " + "lw $t1, " + getAddress(binaryExpRtl.getLeft())  + "\t #left operand value in $t1");
				    mipsOut.println("    " + "sne $t2, $t1, $t0");
				    mipsOut.println("    " + "sw $t2, " + getAddress(evalRtl.getTemp())  + "\t #store word into destination address");
				    break;
				}
				/**
				 * rd = 1 if rs < rt
				 */
			    case EVAL_LT:
				{
				    Eval evalRtl = (Eval)rtlIns;
				    Binary binaryExpRtl = (Binary)evalRtl.getExp();
				    mipsOut.println("    " + "lw $t0, " + getAddress(binaryExpRtl.getRight())  + "\t #right operand value in $t0");
				    mipsOut.println("    " + "lw $t1, " + getAddress(binaryExpRtl.getLeft())  + "\t #left operand value in $t1");
				    mipsOut.println("    " + "slt $t2, $t1, $t0");
				    mipsOut.println("    " + "sw $t2, " + getAddress(evalRtl.getTemp())  + "\t #store word into destination address");
				    break;
				}
			    case EVAL_GT:
				{
				    Eval evalRtl = (Eval)rtlIns;
				    Binary binaryExpRtl = (Binary)evalRtl.getExp();
				    mipsOut.println("    " + "lw $t0, " + getAddress(binaryExpRtl.getRight())  + "\t #right operand value in $t0");
				    mipsOut.println("    " + "lw $t1, " + getAddress(binaryExpRtl.getLeft())  + "\t #left operand value in $t1");
				    mipsOut.println("    " + "sgt $t2, $t1, $t0");
				    mipsOut.println("    " + "sw $t2, " + getAddress(evalRtl.getTemp())  + "\t #store word into destination address");
				    break;
				}
			    case EVAL_LTEQ:
				{
				    Eval evalRtl = (Eval)rtlIns;
				    Binary binaryExpRtl = (Binary)evalRtl.getExp();
				    mipsOut.println("    " + "lw $t0, " + getAddress(binaryExpRtl.getRight())  + "\t #right operand value in $t0");
				    mipsOut.println("    " + "lw $t1, " + getAddress(binaryExpRtl.getLeft())  + "\t #left operand value in $t1");
				    mipsOut.println("    " + "sle $t2, $t1, $t0");
				    mipsOut.println("    " + "sw $t2, " + getAddress(evalRtl.getTemp())  + "\t #store word into destination address");
				    break;
				}
			    case EVAL_GTEQ:
				{
				    Eval evalRtl = (Eval)rtlIns;
				    Binary binaryExpRtl = (Binary)evalRtl.getExp();
				    mipsOut.println("    " + "lw $t0, " + getAddress(binaryExpRtl.getRight())  + "\t #right operand value in $t0");
				    mipsOut.println("    " + "lw $t1, " + getAddress(binaryExpRtl.getLeft())  + "\t #left operand value in $t1");
				    mipsOut.println("    " + "sge $t2, $t1, $t0");
				    mipsOut.println("    " + "sw $t2, " + getAddress(evalRtl.getTemp())  + "\t #store word into destination address");
				    break;
				}
			    case STORE_BYTE:
				{
				    Store storeRtl = (Store)rtlIns;
				    mipsOut.println("    " + "lb $t0, " + getAddress(storeRtl.getVal())  + "\t #value $t0");
				    mipsOut.println("    " + "lw $t1, " + getAddress(storeRtl.getAddr())  + "\t #address in $t1");
				    mipsOut.println("    " + "sb $t0, ($t1)" + "\t #store byte in $t0 into ($t1) address");
				    break;
				}
			    case STORE_WORD:
				{
				    Store storeRtl = (Store)rtlIns;
				    mipsOut.println("    " + "lw $t0, " + getAddress(storeRtl.getVal())  + "\t #value $t0");
				    mipsOut.println("    " + "lw $t1, " + getAddress(storeRtl.getAddr())  + "\t #address in $t1");
				    mipsOut.println("    " + "sw $t0, ($t1)" + "\t #store word in $t0 into ($t1) address");
				    break;
				}
			    case LABDEF:
				{
				    LabDef labDefRtl = (LabDef)rtlIns;
				    mipsOut.println(labDefRtl.getLabel() + ":");
				    if(labDefRtl.isReturnLabel()){
					mipsOut.println("    " + "lw $v0, " + getAddress(Temp.getRV())  + "\t #save the return value to $V0");
					mipsOut.println("    " + "li $t0, "  + (prcDef.getFrame()+ 4));
					mipsOut.println("    " + "sub $t1, $fp, $t0");
					mipsOut.println("    " + "lw $ra, ($t1)" + "\t #restore ra");
					
					mipsOut.println("    " + "move $sp, $fp" + "\t #restore sp");

					mipsOut.println("    " + "li $t0, " + prcDef.getFrame());
					mipsOut.println("    " + "sub $t1, $fp, $t0");
					mipsOut.println("    " + "lw $fp, ($t1)" + "\t #restore fp");

					mipsOut.println("    " + "jr $ra" + "\t #goto the return address");
				    }
				    break;
				}
			    case EVAL_ICON:
			        {
				    Eval evalRtl = (Eval)rtlIns;
				    Icon iConst = (Icon)evalRtl.getExp();
				    mipsOut.println("    " + "li $t0, " + iConst.getVal() +  "\t #load integer  into $t0");
				    mipsOut.println("    " + "sw $t0, " + getAddress(evalRtl.getTemp())  + "\t #store integer in $t0 into " + evalRtl.getTemp());
				    break;
				}
			    case EVAL_LABREF:
			        {
				    Eval evalRtl = (Eval)rtlIns;
				    LabRef lRef = (LabRef)evalRtl.getExp();
				    mipsOut.println("    " + "la $t0, " + lRef.getLabel() +  "\t #load addres of " + lRef.getLabel() +  " into $t0");				    				     
				    mipsOut.println("    " + "sw $t0, " + getAddress(evalRtl.getTemp())  + "\t #store word in $t0 into " + evalRtl.getTemp());
				    break;
				}
			    case EVAL_MOVE:
				{
				    Eval evalRtl = (Eval)rtlIns;
				    TempExp tExp = (TempExp)evalRtl.getExp();
				    mipsOut.println("    " + "lw $t0, " + getAddress(tExp.getTemp())  + "\t #store in $t0 value of " + tExp.getTemp());
				    mipsOut.println("    " + "sw $t0, " + getAddress(evalRtl.getTemp())  + "\t #store word in $t0 into " + evalRtl.getTemp());
				    break;
				}
				
			    case CJUMP_FALSE: 
				{
				    CJump cJumpRtl = (CJump)rtlIns;
				    mipsOut.println("    " + "lw $t0, " + getAddress(cJumpRtl.getTemp())  + "\t #store in $t0 value of " + cJumpRtl.getTemp());
				    mipsOut.println("    " + "beqz $t0, " + cJumpRtl.getLabel()  + "\t #jump if " + cJumpRtl.getTemp() + " contains zero");
				    break;
				}
				
			    case CJUMP_TRUE:
				{
				    CJump cJumpRtl = (CJump)rtlIns;
				    mipsOut.println("    " + "lw $t0, " + getAddress(cJumpRtl.getTemp())  + "\t #store in $t0 value of " + cJumpRtl.getTemp());
				    mipsOut.println("    " + "li $t1, 0" +  "\t #load integer 0 into $t1");
				    mipsOut.println("    " + "bne $t0, $t1, " + cJumpRtl.getLabel()  + "\t #jump if " + cJumpRtl.getTemp() + " does not contain zero");
				    
				    break;
				}
			     
			    default:
				throw new MIPSException();
			    }
			}
			mipsOut.println("#]");
			break;
		    }
		    
	    default:
		throw new MIPSException();
	    }	    
	
	}

	    mipsPputint(mipsOut);
	    mipsPputstring(mipsOut);
	    mipsPgetint(mipsOut);
	    mipsPgetstring(mipsOut);
	    
	    mipsOut.flush();
	    mipsOut.close();
	    oStream.close();	    
	}
	catch(Exception e){
	    System.out.println("Error writing to file");
	}  

    }

    public static void mipsSysCallPre(PrintStream mipsOut) {
	mipsOut.println("#[Save registers and update FP");
	mipsOut.println("    " + "sw $fp, " + "($sp)" + "\t #push fp of caller into stack");
	mipsOut.println("    " + "li $t0, 4");
	mipsOut.println("    " + "sub $sp, $sp, $t0");			
	mipsOut.println("    " + "addi $fp, $sp, " + 4 + "\t #update fp value");
	mipsOut.println("    " + "sw $ra, " + "($sp)" + "\t #push ra of caller into stack");
	mipsOut.println("    " + "li $t0, 4");
	mipsOut.println("    " + "sub $sp, $sp, $t0");
	mipsOut.println("#]");
    }
    
    public static void mipsSysCallBac(PrintStream mipsOut) {
	mipsOut.println("    " + "syscall");
	
	mipsOut.println("#[Restore registers, FP and SP");
	mipsOut.println("    " + "li $t0, "  +  4);
	mipsOut.println("    " + "sub $t1, $fp, $t0");
	mipsOut.println("    " + "lw $ra, ($t1)" + "\t #restore ra");
	mipsOut.println("    " + "move $sp, $fp" + "\t #restore sp");
	mipsOut.println("    " + "lw $fp, ($fp)" + "\t #restore fp");
	mipsOut.println("#]");
	
	mipsOut.println("    " + "jr $ra" + "\t #goto the return address");
    }
    public static void mipsPputint(PrintStream mipsOut){
	mipsOut.println(".globl Pputint");
	mipsOut.println(".text");
	mipsOut.println("Pputint:");
	mipsSysCallPre(mipsOut);
	mipsOut.println("    " + "li $v0, 1");
	mipsOut.println("    " + "lw $a0, 4($fp)");
	mipsSysCallBac(mipsOut);
    }
    public static void mipsPputstring(PrintStream mipsOut){
	mipsOut.println(".globl Pputstring");
	mipsOut.println(".text");
	mipsOut.println("Pputstring:");
	mipsSysCallPre(mipsOut);
	mipsOut.println("    " + "li $v0, 4");
	mipsOut.println("    " + "lw $a0, 4($fp)");
	mipsSysCallBac(mipsOut);
    }
    public static void mipsPgetint(PrintStream mipsOut){
	mipsOut.println(".globl Pgetint");
	mipsOut.println(".text");
	mipsOut.println("Pgetint:");
	mipsSysCallPre(mipsOut);
	mipsOut.println("    " + "li $v0, 5");
	mipsSysCallBac(mipsOut);
    }
    public static void mipsPgetstring(PrintStream mipsOut){
	mipsOut.println(".globl Pgetstring");
	mipsOut.println(".text");
	mipsOut.println("Pgetstring:");
	mipsSysCallPre(mipsOut);
	mipsOut.println("    " + "li $v0, 8");
	mipsOut.println("    " + "lw $a0, 4($fp)");
	mipsOut.println("    " + "li $a1, 80");
	mipsSysCallBac(mipsOut);
    }
    
    public static String getAddress(Temp t)
    {
	return (String) symbolTable.get(t.toString());
    }
    
}